﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Manoso.Data.FluentMap.Converter;

namespace Manoso.Data.FluentMap
{
    public interface IFluentRoot<T> : IFluent<T>, IDisposable
    {
        /// <summary>
        /// The list of top level T objects.
        /// </summary>
        IList<T> Items { get; }

        /// <summary>
        /// Initialise the list of top level T objects with given items.
        /// </summary>
        /// <param name="items">Given IEnumerable of T</param>
        /// <returns></returns>
        IFluentRoot<T> Load(IEnumerable<T> items);

        /// <summary>
        /// Clear all relations but keep existing items.
        /// </summary>
        void Reset();

        /// <summary>
        /// Add a map for the fluent root.
        /// </summary>
        /// <param name="config">Action to config the fluent root node.</param>
        /// <returns></returns>
        IFluent<T> Map(Action<IMap<T>> config);
    }

    internal sealed class FluentRoot<T> : Fluent<T>, IFluentRoot<T>
    {
        public IList<T> Items
        {
            get { return ((IRoot<T>) MapI.Value).Items; }
        }

        public IFluentRoot<T> Load(IEnumerable<T> items)
        {
            if (items == null) return this;

            ((IRoot<T>)MapI.Value).Items.Clear();

            foreach (var item in items)
            {
                ((IRoot<T>)MapI.Value).Items.Add(item);
            }
            return this;
        }

        public void Reset()
        {
            MapI.CommandMaps = new List<IMap>();
        }

        public IFluent<T> Map(Action<IMap<T>> config)
        {
            if (config != null)
                config(MapO);
            return this;
        }

        public void Dispose()
        {
            ((IRoot<T>)MapI.Value).Items = null;
        }

        public string Auto(IMapTask task, Expression<Func<T, bool>> where = null)
        {
            var select = new JoinSelect
            {
                FromTable = MapI.MapTable.Name,
                FromAlias = MapI.MapTable.Alias,
                Where = where.ToSql(task.Adaptor, MapI.MapTable.Alias)
            };
            CreateSelect(task, select, MapI);

            return string.Join(Environment.NewLine, task.Commands);
        }

        public void Read(IMapTask task)
        {
            Restart();

            foreach (var map in MapI.QueryMaps)
            {
                foreach (var query in
                    map.Queries.Where(
                        query => query.Keys != null && query.Keys.Count > 0 && task.IsValid(query.Keys)))
                {
                    map.SetValue(query, task.LoadMap);
                }
            }
        }

        public void Save(IMapTask task)
        {
            var stack = new List<string>();

            SaveLoop(task, MapI, MapI.Value, stack);

            var commands = task.Commands;

            var command = commands.Count <= 0 || commands.Count == task.Declares.Count
                ? null
                : string.Join(string.Empty, commands);
            if (string.IsNullOrWhiteSpace(command)) return;

            task.Submit(command, null);
        }

        #region Private methods

        private void Restart()
        {
            foreach (var map in MapI.QueryMaps.Where(x => x != MapI))
                map.Value = null;
        }

        private void SaveLoop(IMapTask task, IMap map, object value, IEnumerable<string> stack)
        {
            var local = new List<string>(stack);

            var values = map.GetValues(value);
            if (values == null) return;

            foreach (var v in values)
            {
                task.Values[map.MapTable.Alias] = v;
                foreach (var command in map.Commands)
                {
                    command.GetCommand(task, local);
                }

                if (map.CommandMaps == null || map.CommandMaps.Count <= 0) continue;

                foreach (var m in map.CommandMaps)
                {
                    if (m == null || value == null) continue;
                    SaveLoop(task, m, v, local);
                }
            }

            if (map.Parent == MapI)
                task.Complete();
        }

        private void CreateSelect(IMapTask task, IJoinSelect select, IMap map)
        {
            if (map.MapTable != null)
            {
                map.MapTable.Push(select);
            }

            if (map.TableMaps.Count > 0)
            {
                foreach (var m in map.TableMaps)
                {
                    CreateSelect(task, select.Copy, m);
                }
            }
            else if (map.MapTable != null) // the end, create the select command
            {
                var command = map.MapTable.GetCommand(task, select);
                task.AddCommand(command, MapCommandType.SelectJoin);
            }

        }

        #endregion
    }
}